/* Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * Provides HCI transport over sockets. It supports the iot_socket API
 */

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

// Centauri dependencies
#include "cmsis_os2.h"
#include "iot_bluetooth_hci.h"
#include "iot_socket.h"

#define PW_LOG_MODULE_NAME "HCI_CTRL_IOT_SOCK"
#include "pw_log/log.h"

#ifndef BLE_CONTROLLER_TRANSPORT_SOCKET_IP_PORT
#define BLE_CONTROLLER_TRANSPORT_SOCKET_IP_PORT 51051 // Arbitrary dynamic port
#endif
#ifndef BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_1
#define BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_1 127
#endif
#ifndef BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_2
#define BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_2 0
#endif
#ifndef BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_3
#define BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_3 0
#endif
#ifndef BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_4
#define BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_4 1
#endif
#ifndef BLE_CONTROLLER_HCI_RX_THREAD_STACK_SIZE
#define BLE_CONTROLLER_HCI_RX_THREAD_STACK_SIZE 1024
#endif

#ifndef htons

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define htons(x) ((uint16_t)((((x)&0xff00) >> 8) | (((x)&0x00ff) << 8)))
#else
#define htons(x) ((uint16_t)(x))
#endif

#endif

#define INTERNAL_BUFFERS_SIZE 512

struct ipv4_address {
    uint8_t ip[4];
};

static struct ble_hci_sock_state {
    int sock;
    uint8_t rx_data[INTERNAL_BUFFERS_SIZE];
    uint8_t tx_data[INTERNAL_BUFFERS_SIZE];
} ble_hci_sock_state;

static osThreadId_t rx_thread = NULL;
static iot_hci_host_rx_callback rx_callback;

static void handle_rx(void *arg)
{
    (void)arg;
    assert(rx_callback);

    while (1) {
        int len;

        if (ble_hci_sock_state.sock < 0) {
            PW_LOG_ERROR("Socket closed, killing blutooth RX thread");
            osThreadExit();
        }

        len = iotSocketRecv(ble_hci_sock_state.sock, ble_hci_sock_state.rx_data, sizeof(ble_hci_sock_state.rx_data));
        if (len > 0) {
            rx_callback(ble_hci_sock_state.rx_data, len);
        }
    }
}

static osStatus_t connect_socket_to_remote_controller(struct ipv4_address ipv4, uint16_t port)
{
    PW_LOG_DEBUG("Connecting to %hhu.%hhu.%hhu.%hhu:%hu", ipv4.ip[0], ipv4.ip[1], ipv4.ip[2], ipv4.ip[3], port);

    if (ble_hci_sock_state.sock < 0) {
        int32_t socket = iotSocketCreate(IOT_SOCKET_AF_INET, IOT_SOCKET_SOCK_STREAM, IOT_SOCKET_IPPROTO_TCP);
        if (socket < 0) {
            PW_LOG_ERROR("Failed to create socket (errno %ld)", socket);
            return osError;
        }

        int32_t socket_status = iotSocketConnect(socket, (uint8_t *)ipv4.ip, sizeof(ipv4.ip), port);

        if (socket_status) {
            int num_retries = 5;
            for (int i = 0; i < num_retries; i++) {
                osDelay(osKernelGetTickFreq() / 2); // wait 0.5sec
                PW_LOG_DEBUG("Failed to connect, retrying...");
                socket_status = iotSocketConnect(socket, (uint8_t *)ipv4.ip, sizeof(ipv4.ip), port);
                if (socket_status == 0) {
                    break;
                }
            }
            if (socket_status != 0) {
                PW_LOG_ERROR("iotSocketConnect() failed Is the server socket on the other side open?");
                iotSocketClose(socket);
                return osError;
            }
        }

        ble_hci_sock_state.sock = socket;
    }

    const osThreadAttr_t thread_attr = {.name = "BLE HCI RX", .stack_size = BLE_CONTROLLER_HCI_RX_THREAD_STACK_SIZE};

    rx_thread = osThreadNew(handle_rx, NULL, &thread_attr);
    if (rx_thread == NULL) {
        return osError;
    }

    return osOK;
}

/**
 * Initializes the HCI transport module.
 */
static osStatus_t hci_transport_driver_init(void)
{
    if (rx_thread) {
        return osError;
    }

    memset(&ble_hci_sock_state, 0, sizeof(ble_hci_sock_state));
    ble_hci_sock_state.sock = -1;
    struct ipv4_address ipv4 = {.ip = {BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_1,
                                       BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_2,
                                       BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_3,
                                       BLE_CONTROLLER_TRANSPORT_SOCKET_IP_OCTET_4}};
    uint16_t port = BLE_CONTROLLER_TRANSPORT_SOCKET_IP_PORT;

    return connect_socket_to_remote_controller(ipv4, port);
}

/* iot_bluetooth_hci API implementation */

int32_t iotHciTransportInit(iot_hci_host_rx_callback callback)
{
    rx_callback = callback;
    return (int32_t)hci_transport_driver_init();
}

int32_t iotHciSendToController(const iot_hci_message_t *message)
{
    assert(message);

    int32_t ret = osOK;

    uint8_t *send_buffer;
    size_t send_buffer_size;
    // if type is not included in the buffer we need to send it first
    if (!message->type_included_in_packet) {
        send_buffer_size = message->packet_size + 1;
        send_buffer = ble_hci_sock_state.tx_data;
        if (send_buffer_size > INTERNAL_BUFFERS_SIZE) {
            PW_LOG_ERROR(
                "Trying to send too much data to controller: %zu (max: %d)", send_buffer_size, INTERNAL_BUFFERS_SIZE);
            return osError;
        }
        send_buffer[0] = message->type;
        memcpy(&send_buffer[1], message->packet, message->packet_size);
    } else {
        send_buffer = (uint8_t *)message->packet;
        send_buffer_size = message->packet_size;
    }

    uint32_t total_sent = 0;
    while (total_sent < send_buffer_size) {
        int32_t sent = iotSocketSend(ble_hci_sock_state.sock, send_buffer + total_sent, send_buffer_size - total_sent);
        if (sent < 0) {
            PW_LOG_ERROR("Failure to send HCI to the controller (error: %ld)", sent);
            ret = osError;
            break;
        }
        total_sent += sent;
    }

    return ret;
}
